/*
    Copyright © 2013 by Maxim Biro <nurupo.contributions@gmail.com>
    Copyright © 2014-2019 by The qTox Project Contributors

    This file is part of qTox, a Qt-based graphical interface for Tox.

    qTox is libre software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    qTox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with qTox.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include "coreav.h"
#include "audio/iaudiosettings.h"
#include "core.h"
#include "model/friend.h"
//#include "model/group.h"
#include "video/corevideosource.h"
#include <thread>
#include <cassert>
#include <shared_mutex>

/**
 * @fn void CoreAV::avInvite(uint32_t friendId, bool video)
 * @brief Sent when a friend calls us.
 * @param friendId Id of friend in call list.
 * @param video False if chat is audio only, true audio and video.
 *
 * @fn void CoreAV::avStart(uint32_t friendId, bool video)
 * @brief Sent when a call we initiated has started.
 * @param friendId Id of friend in call list.
 * @param video False if chat is audio only, true audio and video.
 *
 * @fn void CoreAV::avEnd(uint32_t friendId)
 * @brief Sent when a call was ended by the peer.
 * @param friendId Id of friend in call list.
 *
 * @var CoreAV::VIDEO_DEFAULT_BITRATE
 * @brief Picked at random by fair dice roll.
 */

/**
 * @var std::atomic_flag CoreAV::threadSwitchLock
 * @brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV
 * thread.
 *
 * The CoreAV thread must have priority for the flag, other threads should back off or release it
 * quickly.
 * CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
 * toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
 * most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
 * and which we call via signals.
 * When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
 * when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
 * switch to the UI thread to send it a signal. Both switches block both threads, so this would
 * deadlock.
 */

CoreAV::CoreAV(Core* core, std::unique_ptr<ToxAV, ToxAVDeleter> toxav,
               IAudioSettings& _audioSettings)
    : audio{nullptr}
    , toxav{std::move(toxav)}
    , iterateTimer{}
    , core(core)
    , audioSettings{_audioSettings}
{
    connectCallbacks(*this->toxav);


}

void CoreAV::connectCallbacks(ToxAV& toxav)
{
    toxav_callback_call(&toxav, CoreAV::callCallback, this);
    toxav_callback_call_state(&toxav, CoreAV::stateCallback, this);
    toxav_callback_audio_bit_rate(&toxav, CoreAV::audioBitrateCallback, this);
    toxav_callback_video_bit_rate(&toxav, CoreAV::videoBitrateCallback, this);
    toxav_callback_audio_receive_frame(&toxav, CoreAV::audioFrameCallback, this);
    toxav_callback_video_receive_frame(&toxav, CoreAV::videoFrameCallback, this);
    //toxav_callback_video_receive_frame2(&toxav, CoreAV::videoFrameCallback2, this);
}

/**
 * @brief Factory method for CoreAV
 * @param core pointer to the Tox instance
 * @return CoreAV instance on success, {} on failure
 */
CoreAV::CoreAVPtr CoreAV::makeCoreAV(Core * core, Tox* tox,
                                     IAudioSettings& audioSettings)
{
    Toxav_Err_New err;
    std::unique_ptr<ToxAV, ToxAVDeleter> toxav{toxav_new(tox, &err)};
    switch (err) {
    case TOXAV_ERR_NEW_OK:
        break;
    case TOXAV_ERR_NEW_MALLOC:
        std::cout << "Failed to allocate resources for ToxAV" << std::endl;
        return {};
    case TOXAV_ERR_NEW_MULTIPLE:
        std::cout << "Attempted to create multiple ToxAV instances" << std::endl;
        return {};
    case TOXAV_ERR_NEW_NULL:
        std::cout << "Unexpected NULL parameter" << std::endl;
        return {};
    }

    assert(toxav != nullptr);

    return CoreAVPtr{new CoreAV{core,std::move(toxav), audioSettings}};
}

/**
 * @brief Set the audio backend
 * @param audio The audio backend to use
 * @note This must be called before starting CoreAV and audio must outlive CoreAV
 */
void CoreAV::setAudio(IAudioControl& newAudio)
{
    audio.exchange(&newAudio);
}

/**
 * @brief Get the audio backend used
 * @return Pointer to the audio backend
 * @note This is needed only for the case CoreAV needs to restart and the restarting class doesn't
 * have access to the audio backend and wants to keep it the same.
 */
IAudioControl* CoreAV::getAudio()
{
    return audio;
}

CoreAV::~CoreAV()
{
    /* Gracefully leave calls and group calls to avoid deadlocks in destructor */
    for (const auto& call : calls) {
        cancelCall(call.first);
    }
    assert(calls.empty());

}

/**
 * @brief Starts the CoreAV main loop that calls toxav's main loop
 */
void CoreAV::start()
{
    iterateTimer.add(std::chrono::microseconds(50), std::bind(&CoreAV::process, this));
    //iterateTimer.start(50, std::bind(&CoreAV::process, this));
}

void CoreAV::process()
{
    toxav_iterate(toxav.get());
    uint32_t interval =toxav_iteration_interval(toxav.get());
    iterateTimer.add(std::chrono::microseconds(interval), std::bind(&CoreAV::process, this));
}

/**
 * @brief Checks the call status for a Tox friend.
 * @param f the friend to check
 * @return true, if call is started for the friend, false otherwise
 */
bool CoreAV::isCallStarted(const Friend* f) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);
    return f && (calls.find(f->getId()) != calls.end());
}



/**
 * @brief Checks the call status for a Tox friend.
 * @param f the friend to check
 * @return true, if call is active for the friend, false otherwise
 */
bool CoreAV::isCallActive(const Friend* f) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    auto it = calls.find(f->getId());
    if (it == calls.end()) {
        return false;
    }
    return isCallStarted(f) && it->second->isActive();
}



bool CoreAV::isCallVideoEnabled(const Friend* f) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    auto it = calls.find(f->getId());
    return isCallStarted(f) && it->second->getVideoEnabled();
}

bool CoreAV::answerCall(uint32_t friendNum, bool video)
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);
    std::unique_lock<std::recursive_mutex> coreLocker(core->coreLoopLock);

    std::cout <<"Answering call " <<friendNum << std::endl;
    auto it = calls.find(friendNum);
    assert(it != calls.end());
    Toxav_Err_Answer err;

    const uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
    if (toxav_answer(toxav.get(), friendNum, audioSettings.getAudioBitrate(),
                     videoBitrate, &err)) {
        it->second->setActive(true);
        return true;
    } else {
        std::cout << "Failed to answer call with error " << err << std::endl;
        toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
        calls.erase(it);
        return false;
    }
}

bool CoreAV::startCall(uint32_t friendNum, bool video)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);
    std::unique_lock< std::recursive_mutex > coreLocker(core->coreLoopLock);

    //std::cout <<"Starting call with "<<friendNum << std::endl;
    auto it = calls.find(friendNum);
    if (it != calls.end()) {
        std::cout << "Can't start call with "<< friendNum <<", we're already in this call!" << std::endl;
        return false;
    }

    uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
    if (!toxav_call(toxav.get(), friendNum, audioSettings.getAudioBitrate(), videoBitrate,
                    nullptr))
        return false;

    // Audio backend must be set before making a call
    assert(audio != nullptr);
    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this, *audio,false));
    // Call object must be owned by this thread or there will be locking problems with Audio
    //call->moveToThread(this->thread());
    assert(call != nullptr);
    calls.emplace(friendNum, std::move(call));
    return true;
}

bool CoreAV::cancelCall(uint32_t friendNum)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);
    std::unique_lock< std::recursive_mutex > coreLocker(core->coreLoopLock);

    std::cout <<"Cancelling call with "<<friendNum << std::endl;
    if (!toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr)) {
        std::cout << "Failed to cancel call with "<<friendNum << std::endl;
        return false;
    }

    calls.erase(friendNum);
    locker.unlock();

    avEnd(friendNum,false);
    return true;
}

void CoreAV::timeoutCall(uint32_t friendNum)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);

    if (!cancelCall(friendNum)) {
       std::cout <<"Failed to timeout call with "<<friendNum << std::endl;
        return;
    }
    std::cout<<"Call with friend " << friendNum << " timed out" << std::endl;
}

/**
 * @brief Send audio frame to a friend
 * @param callId Id of friend in call list.
 * @param pcm An array of audio samples (Pulse-code modulation).
 * @param samples Number of samples in this frame.
 * @param chans Number of audio channels.
 * @param rate Audio sampling rate used in this frame.
 * @return False only on error, but not if there's nothing to send.
 */
bool CoreAV::sendCallAudio(uint32_t callId, const int16_t* pcm, size_t samples, uint8_t chans,
                           uint32_t rate) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    auto it = calls.find(callId);
    if (it == calls.end()) {
        return false;
    }

    ToxFriendCall const& call = *it->second;

    if (call.getMuteMic() || !call.isActive()
        || !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A)) {
        return true;
    }

    // TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case
    Toxav_Err_Send_Frame err;
    int retries = 0;
    do {
        if (!toxav_audio_send_frame(toxav.get(), callId, pcm, samples, chans, rate, &err)) {
            if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
                ++retries;
                std::this_thread::sleep_for(std::chrono::milliseconds(500));
            } else {
                std::cout << "toxav_audio_send_frame error: " << err << std::endl;
            }
        }
    } while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
    if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
        std::cout << "toxav_audio_send_frame error: Lock busy, dropping frame" << std::endl;
    }

    return true;
}

void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<vpx_image_t> yuvframe)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);

    // We might be running in the FFmpeg thread and holding the CameraSource lock
    // So be careful not to deadlock with anything while toxav locks in toxav_video_send_frame
    auto it = calls.find(callId);
    if (it == calls.end()) {
        return;
    }

    ToxFriendCall& call = *it->second;

    if (!call.getVideoEnabled() || !call.isActive()
        || !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V)) {
        return;
    }

    if (call.getNullVideoBitrate()) {
       std::cout<< "Restarting video stream to friend" << callId << std::endl;
       std::unique_lock<std::recursive_mutex > coreLocker(core->coreLoopLock);
        toxav_video_set_bit_rate(toxav.get(), callId, VIDEO_DEFAULT_BITRATE, nullptr);
        call.setNullVideoBitrate(false);
    }

    //utox_av_video_frame yuvframe;
    //vpx_image_t* input=nullptr;
    //vpx_img_alloc(input, VPX_IMG_FMT_I420, vframe->w, vframe->h, 1);

    //if (!input) {
    //    return;
    //};

    //yuvframe.y = input->planes[0];
    //yuvframe.u = input->planes[1];
    //yuvframe.v = input->planes[2];
    //yuvframe.w = input->d_w;
    //yuvframe.h = input->d_h;
    //bgrxtoyuv420(yuvframe.y, yuvframe.u, yuvframe.v,(uint8_t*)vframe->img, vframe->w, vframe->h);



    // TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case
    // We don't want to be dropping iframes because of some lock held by toxav_iterate
    Toxav_Err_Send_Frame err;
    int retries = 0;
    do {
        if (!toxav_video_send_frame(toxav.get(), callId, yuvframe->w, yuvframe->h, yuvframe->planes[0],
            yuvframe->planes[1], yuvframe->planes[2], &err)) {
        //if (!toxav_video_send_frame2(toxav.get(), callId, frame, &err)) {

            if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
                ++retries;
                std::this_thread::sleep_for(std::chrono::milliseconds(500));
            } else {
                std::cout << "toxav_video_send_frame error: " << err << std::endl;
            }
        }
    } while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
    if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
        std::cout << "toxav_video_send_frame error: Lock busy, dropping frame" << std::endl;
    }
}

/**
 * @brief Toggles the mute state of the call's input (microphone).
 * @param f The friend assigned to the call
 */
void CoreAV::toggleMuteCallInput(const Friend* f)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);

    auto it = calls.find(f->getId());
    if (f && (it != calls.end())) {
        ToxCall& call = *it->second;
        call.setMuteMic(!call.getMuteMic());
    }
}

/**
 * @brief Toggles the mute state of the call's output (speaker).
 * @param f The friend assigned to the call
 */
void CoreAV::toggleMuteCallOutput(const Friend* f)
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);

    auto it = calls.find(f->getId());
    if (f && (it != calls.end())) {
        ToxCall& call = *it->second;
        call.setMuteVol(!call.getMuteVol());
    }
}


/**
 * @brief Get a call's video source.
 * @param friendNum Id of friend in call list.
 * @return Video surface to show
 */
VideoSource* CoreAV::getVideoSourceFromCall(int friendNum) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    auto it = calls.find(friendNum);
    if (it == calls.end()) {
        std::cout << "CoreAV::getVideoSourceFromCall: No such call, possibly cancelled" << std::endl;
        return nullptr;
    }

    return it->second->getVideoSource();
}



/**
 * @brief Returns the calls input (microphone) mute state.
 * @param f The friend to check
 * @return true when muted, false otherwise
 */
bool CoreAV::isCallInputMuted(const Friend* f) const
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    if (!f) {
        return false;
    }
    const uint32_t friendId = f->getId();
    auto it = calls.find(friendId);
    return (it != calls.end()) && it->second->getMuteMic();
}

/**
 * @brief Returns the calls output (speaker) mute state.
 * @param friendId The friend to check
 * @return true when muted, false otherwise
 */
bool CoreAV::isCallOutputMuted(const Friend* f) const
{
    std::unique_lock< std::recursive_mutex > locker(callsLock);

    if (!f) {
        return false;
    }
    const uint32_t friendId = f->getId();
    auto it = calls.find(friendId);
    return (it != calls.end()) && it->second->getMuteVol();
}

/**
 * @brief Signal to all peers that we're not sending video anymore.
 * @note The next frame sent cancels this.
 */
void CoreAV::sendNoVideo()
{
    std::unique_lock<std::recursive_mutex> locker(callsLock);

    // We don't change the audio bitrate, but we signal that we're not sending video anymore
    std::cout << "CoreAV: Signaling end of video sending" << std::endl;
    for (auto& kv : calls) {
        ToxFriendCall& call = *kv.second;
        toxav_video_set_bit_rate(toxav.get(), kv.first, 0, nullptr);
        call.setNullVideoBitrate(true);
    }
}

void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool video, void* vSelf)
{
    CoreAV* self = static_cast<CoreAV*>(vSelf);
    std::unique_lock<std::recursive_mutex> locker(self->callsLock);


    // Audio backend must be set before receiving a call
    assert(self->audio != nullptr);
    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self, *self->audio,true});
    // Call object must be owned by CoreAV thread or there will be locking problems with Audio
   // call->moveToThread(self->thread());
    assert(call != nullptr);

    auto it = self->calls.emplace(friendNum, std::move(call));
    if (it.second == false) {
        std::cout<< "Rejecting call invite from "<< friendNum<<" we're already in that call!" << std::endl;
        toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
        return;
    }
    std::cout << "Received call invite from "<<friendNum << std::endl;

    // We don't get a state callback when answering, so fill the state ourselves in advance
    int state = 0;
    if (audio)
        state |= TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_ACCEPTING_A;
    if (video)
        state |= TOXAV_FRIEND_CALL_STATE_SENDING_V | TOXAV_FRIEND_CALL_STATE_ACCEPTING_V;
    it.first->second->setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));

    // Must explicitely unlock, because a deadlock can happen via ChatForm/Audio
    locker.unlock();

    self->avInvite(friendNum, video);
}

void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, void* vSelf)
{
    //Q_UNUSED(toxav)
    CoreAV* self = static_cast<CoreAV*>(vSelf);

    // we must unlock this lock before emitting any signals
    std::unique_lock<std::recursive_mutex> locker(self->callsLock);

    auto it = self->calls.find(friendNum);
    if (it == self->calls.end()) {
        std::cout <<"stateCallback called, but call "<< friendNum <<" is already dead" << std::endl;
        return;
    }

    ToxFriendCall& call = *it->second;

    if (state & TOXAV_FRIEND_CALL_STATE_ERROR) {
        std::cout << "Call with friend " << friendNum << " died of unnatural causes!" << std::endl;
        self->calls.erase(friendNum);
        locker.unlock();
        self->avEnd(friendNum, true);
    } else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED) {
        std::cout << "Call with friend " << friendNum << " finished quietly" << std::endl;
        self->calls.erase(friendNum);
        locker.unlock();
        self->avEnd(friendNum,false);
    } else {
        // If our state was null, we started the call and were still ringing
        if (!call.getState() && state) {
            call.setActive(true);
            bool videoEnabled = call.getVideoEnabled();
            call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
            locker.unlock();
            self->avStart(friendNum, videoEnabled);
        } else if ((call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V)
                   && !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
            std::cout  << "Friend " << friendNum << "stopped sending video" << std::endl;
            if (call.getVideoSource()) {
                call.getVideoSource()->stopSource();
            }

            call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
        } else if (!(call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V)
                   && (state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
            // Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending
            // video"
            // out of orders (even though they were sent in order by the other end).
            // We simply stop the videoSource from emitting anything while the other end says it's
            // not sending
            if (call.getVideoSource()) {
                call.getVideoSource()->restartSource();
            }

            call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
        }
    }
}

// This is only a dummy implementation for now
void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate,
                             void* vSelf)
{
    CoreAV* self = static_cast<CoreAV*>(vSelf);

    std::cout  << "Recommended bitrate with " << friendNum << " is now " << arate << "/" << vrate
             << ", ignoring it" << std::endl;
}

// This is only a dummy implementation for now
void CoreAV::audioBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf)
{
    CoreAV* self = static_cast<CoreAV*>(vSelf);

    std::cout << "Recommended audio bitrate with " << friendNum << " is now " << rate << ", ignoring it" << std::endl;
}

// This is only a dummy implementation for now
void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf)
{
    CoreAV* self = static_cast<CoreAV*>(vSelf);


    std::cout << "Recommended video bitrate with " << friendNum << " is now " << rate << ", ignoring it" << std::endl;
}

void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, size_t sampleCount,
                                uint8_t channels, uint32_t samplingRate, void* vSelf)
{

    CoreAV* self = static_cast<CoreAV*>(vSelf);
    //self->coreavThreadPool.enqueue([self, friendNum, pcm, sampleCount, channels, samplingRate]() {
    // This callback should come from the CoreAV thread
    //assert(std::this_thread::get_id() == self->coreavThread->get_id());

        std::unique_lock<std::recursive_mutex> locker(self->callsLock);

        auto it = self->calls.find(friendNum);
        if (it == self->calls.end()) {
            return;
        }

        ToxFriendCall& call = *it->second;

        if (call.getMuteVol()) {
            return;
        }

        call.playAudioBuffer(pcm, sampleCount, channels, samplingRate);
   // });
}

void CoreAV::videoFrameCallback(ToxAV*, uint32_t friendNum, uint16_t w, uint16_t h,
                                const uint8_t* y, const uint8_t* u, const uint8_t* v,
                                int32_t ystride, int32_t ustride, int32_t vstride, void* vSelf)
{
    auto self = static_cast<CoreAV*>(vSelf);
    //self->coreavThreadPool.enqueue([self, friendNum,]() {
        // This callback should come from the CoreAV thread
        //assert(std::this_thread::get_id() == self->coreavThread->get_id());
        std::unique_lock<std::recursive_mutex> locker(self->callsLock);
        auto it = self->calls.find(friendNum);
        if (it == self->calls.end()) {
            return;
        }

        CoreVideoSource* videoSource = it->second->getVideoSource();
        if (!videoSource) {
            return;
        }

        vpx_image *frame = new vpx_image();
        frame->d_h = h;
        frame->d_w = w;
        frame->planes[0] = const_cast<uint8_t*>(y);
        frame->planes[1] = const_cast<uint8_t*>(u);
        frame->planes[2] = const_cast<uint8_t*>(v);
        frame->stride[0] = ystride;
        frame->stride[1] = ustride;
        frame->stride[2] = vstride;

        videoSource->pushFrame(frame);
    //});
}
